﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="DenseVectorExtensions.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra.Sequential
{
    #region Usings

    using System;
    using System.Globalization;

    using ExulLibrary.Exceptions;

    #endregion

    /// <summary>
    ///     Provides extension methods for <see cref="DenseVector{T}"/>.
    /// </summary>
    public static class DenseVectorExtensions
    {
        #region Methods

        #region public

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign<T>(this DenseVector<T> target, DenseVector<T> source)
        {
            #if SAFE

            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            #endif

            Array.Copy(source.Values, target.Values, Math.Min(target.Values.Length, source.Values.Length));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<bool> target, DenseVector<bool> source)
        {
            Assign(target, source, sizeof(bool));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<byte> target, DenseVector<byte> source)
        {
            Assign(target, source, sizeof(byte));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<char> target, DenseVector<char> source)
        {
            Assign(target, source, sizeof(char));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<double> target, DenseVector<double> source)
        {
            Assign(target, source, sizeof(double));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<short> target, DenseVector<short> source)
        {
            Assign(target, source, sizeof(short));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<int> target, DenseVector<int> source)
        {
            Assign(target, source, sizeof(int));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<long> target, DenseVector<long> source)
        {
            Assign(target, source, sizeof(long));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        [CLSCompliant(false)]
        public static void Assign(this DenseVector<sbyte> target, DenseVector<sbyte> source)
        {
            Assign(target, source, sizeof(sbyte));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign(this DenseVector<float> target, DenseVector<float> source)
        {
            Assign(target, source, sizeof(float));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        [CLSCompliant(false)]
        public static void Assign(this DenseVector<ushort> target, DenseVector<ushort> source)
        {
            Assign(target, source, sizeof(ushort));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        [CLSCompliant(false)]
        public static void Assign(this DenseVector<uint> target, DenseVector<uint> source)
        {
            Assign(target, source, sizeof(uint));
        }

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        [CLSCompliant(false)]
        public static void Assign(this DenseVector<ulong> target, DenseVector<ulong> source)
        {
            Assign(target, source, sizeof(ulong));
        }

        /// <summary>
        ///     Assigns elements of the second vector to the specified elements of the first vector.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="targetIndexes">
        ///     The collection of zero-based indexes of target elements.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="targetIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     An element of <paramref name="targetIndexes"/> is negative or greater than or equal to the number of elements of
        ///     <paramref name="target"/>.
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="targetIndexes"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        public static void Assign<T>(this DenseVector<T> target, int[] targetIndexes, DenseVector<T> source)
        {
            #if SAFE

            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            if (targetIndexes == null)
            {
                throw new ArgumentNullException("targetIndexes");
            }

            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            #endif

            var targetValues = target.Values;
            var sourceValues = source.Values;

            var length = Math.Min(targetIndexes.Length, source.Length);
            for (var index = 0; index < length; index++)
            {
                var elementIndex = targetIndexes[index];

                #if SAFE

                if (elementIndex < 0 || target.Length <= elementIndex)
                {
                    var argumentName = string.Format(CultureInfo.CurrentCulture, "targetIndexes[{0}]", index);
                    throw new ElementIndexIsOutOfRangeException(argumentName, index, 0, target.Length - 1);
                }

                #endif

                targetValues[elementIndex] = sourceValues[index];
            }
        }

        /// <summary>
        ///     Sets the default value to all elements of the matrix.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="vector">
        ///     The vector that contains elements to clear.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="vector"/> is <see langword="null"/>.
        /// </exception>
        public static void Clear<T>(this DenseVector<T> vector)
        {
            #if SAFE

            if (vector == null)
            {
                throw new ArgumentNullException("vector");
            }

            #endif

            Array.Clear(vector.Values, 0, vector.Length);
        }

        /// <summary>
        ///     Creates a deep copy of the vector.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="source">
        ///     The source vector.
        /// </param>
        /// <returns>
        ///     A deep copy of the vector.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
        public static DenseVector<T> Clone<T>(this DenseVector<T> source)
        {
            #if SAFE

            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            #endif

            var result = new DenseVector<T>(source.Length);
            result.Assign(source);
            return result;
        }

        /// <summary>
        ///     Gets the element in the vector at the specified location.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="vector">
        ///     The vector that contains the element to get.
        /// </param>
        /// <param name="index">
        ///     The zero-based index of the element to get.
        /// </param>
        /// <returns>
        ///     The element in the vector at the specified location.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="vector"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     <paramref name="index"/> is negative or greater than or equal to
        ///     the number of elements of <paramref name="vector"/>.
        /// </exception>
        public static T GetAt<T>(this DenseVector<T> vector, int index)
        {
            #if SAFE

            if (vector == null)
            {
                throw new ArgumentNullException("vector");
            }

            if ((index < 0) || (vector.Length <= index))
            {
                throw new ElementIndexIsOutOfRangeException("index", index, 0, vector.Length - 1);
            }

            #endif

            return vector.Values[index];
        }

        /// <summary>
        ///     Sets the element in the vector at the specified location.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="vector">
        ///     The vector that contains the element to set.
        /// </param>
        /// <param name="newValue">
        ///     The new value for the element.
        /// </param>
        /// <param name="index">
        ///     The zero-based index of the element to set.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="vector"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     <paramref name="index"/> is negative or greater than or equal to
        ///     the number of elements of <paramref name="vector"/>.
        /// </exception>
        public static void SetAt<T>(this DenseVector<T> vector, T newValue, int index)
        {
            #if SAFE

            if (vector == null)
            {
                throw new ArgumentNullException("vector");
            }

            if ((index < 0) || (vector.Length <= index))
            {
                throw new ElementIndexIsOutOfRangeException("index", index, 0, vector.Length - 1);
            }

            #endif

            vector.Values[index] = newValue;
        }

        #endregion

        #region private

        /// <summary>
        ///     Assigns the second vector to the first vector.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of vector elements.
        /// </typeparam>
        /// <param name="target">
        ///     The vector whose elements should be assigned.
        /// </param>
        /// <param name="source">
        ///     The vector whose elements should be copied.
        /// </param>
        /// <param name="itemSize">
        ///     The size of one elements in bytes.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="target"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="source"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of elements of <paramref name="source"/> to assign to <paramref name="target"/> is determined as
        ///     min(<paramref name="target"/>.Length, <paramref name="source"/>.Length).
        /// </remarks>
        private static void Assign<T>(DenseVector<T> target, DenseVector<T> source, int itemSize)
        {
            #if SAFE

            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            #endif

            Buffer.BlockCopy(source.Values, 0, target.Values, 0, itemSize * Math.Min(target.Values.Length, source.Values.Length));
        }

        #endregion

        #endregion
    }
}